﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Sherwood.Security;
using System.Xml;
using Sherwood.SignOn.Server.Models.Repositories;
using Sherwood.SignOn.Server.Models.Services;

namespace Sherwood.SignOn.Server.Models.ViewModels
{
    /// <summary>
    /// View model base for Services actions.
    /// Provides a method for verifying a client request.
    /// </summary>
    public abstract class ServicesViewModel : ViewModelBase
    {
        public string ClientCode { get; set; }
        public string Timestamp { get; set; }
        public string Signature { get; set; }


        /// <summary>
        /// Checks the ClientCode / Timestamp parameters against the Signature parameter in order to verify the identity
        /// of the client.
        /// In order to return true:
        /// - The timestamp must be within a certain period of time from the current time
        /// - The client code must corrospond with the code of a known client for which the sytem has a public Rsa key.
        /// - The signature must be an SHA hash of ClientCode|Timestamp, encrypted using the clients private Rsa key.
        /// </summary>
        /// <returns>True if signature, timestamp and client code could be verified, otherwise false.</returns>
        public bool VerifyClient()
        {
            if (!String.IsNullOrEmpty(ClientCode) && !String.IsNullOrEmpty(Timestamp) && !String.IsNullOrEmpty(Signature))
            {
                DateTime utcTimestamp = DateTime.MinValue;
                utcTimestamp = DateTime.ParseExact(Timestamp, "yyyy-MM-ddTHH:mm:ss.fffZ", System.Globalization.CultureInfo.InvariantCulture);

                //Find the difference between the current time and the timestamp sent with the request.
                //If dates on both client and server are set correctly, timestamp should be before DateTime.Now
                //However, we assume that timestamp can also be after DateTime.Now due to incorrectly set dates.
                //We accept requests within a certain timeframe (as defined in web.config)
                int secondSinceRequest = (int)Math.Abs((DateTime.Now - utcTimestamp).TotalSeconds);
                if (secondSinceRequest > Config.Settings.RequestValidityInSeconds)
                {
                    throw new Exception("Invalid timestamp. Seconds since request: " + secondSinceRequest);
                    //return false;
                }
                else
                {
                    bool signatureVerified = Data.Clients.VerifyMessage(ClientCode, SignatureContent, Signature);
                    if (!signatureVerified)
                        throw new Exception("Invalid signature.");
                    return signatureVerified;

                }

            }
            else
            {
                throw new Exception("ClientCode, timestamp or signature not provided.");
                //return false;
            }
        }

        /// <summary>
        /// Message signatures should always sign the following content: 
        /// ClientCode | Timestamp | <see cref="SignatureContent"/>
        /// 
        /// In classes that inherit from this class, SignatureContent should return the 
        /// SignatureContent part of the signature source.
        /// </summary>
        public abstract string SignatureContent { get; }
    }


    /// <summary>
    /// View model for Authentication service
    /// </summary>
    public class AuthenticateServiceViewModel : ServicesViewModel
    {
        public string UserName { get; set; }
        public string Password { get; set; }

        /// <summary>
        /// Authenticates a user using the username / password parameters
        /// </summary>
        /// <returns>UserAccount if user could be authenticated, otherwise null</returns>
        public IUserAccount AuthenticateUser()
        {
            if (VerifyClient())
            {
                if (!string.IsNullOrEmpty(UserName) && !String.IsNullOrEmpty(Password))
                {
                    return Data.UserAccounts.AuthenticateUser(UserName, Password);
                }
            }
            return null;
        }


        public override string SignatureContent
        {
            get { return ClientCode + "|" + Timestamp + "|" + UserName + "|" + Password; }
        }
    }


    /// <summary>
    /// View model for service that returns UserProfile
    /// </summary>
    public class GetUserProfileServiceViewModel : ServicesViewModel
    {
        public string UserAccountId { get; set; }
        public string UserName {get; set;}
        public string Email { get; set; }

        /// <summary>
        /// Attempts to find a user profile that corrosponds to either UserAccountId, UserName or Email
        /// </summary>
        /// <returns>UserAccount if request has been verified and user found, otherwise null.</returns>
        public IUserAccount GetUserProfile()
        {
            if (VerifyClient())
            {
                if (!String.IsNullOrEmpty(UserAccountId))
                {
                    return Data.UserAccounts.GetUserAccount(new Guid(UserAccountId));
                }
                else
                {
                    Email = Email == null ? "" : Email.Trim().ToLower();
                    UserName = UserName == null ? "" : UserName.Trim().ToLower();
                    if (!String.IsNullOrEmpty(Email) || !String.IsNullOrEmpty(UserName))
                    {
                        return Data.UserAccounts.GetUserAccountByUsernameOrEmail(UserName, Email);
                    }
                }
            }
            return null;
        }

        public override string SignatureContent
        {
            get { return ClientCode + "|" + Timestamp + "|" + UserAccountId + "|" + UserName + "|" + Email; }
        }
    }

    /// <summary>
    /// View model for service that checks if session is active.
    /// </summary>
    public class IsSessionActiveViewModel : ServicesViewModel
    {
        public string SignOnSessionId { get; set; }

        /// <summary>
        /// Checks if an active session is associated with the UserSessionId parameter.
        /// </summary>
        /// <returns>UserAccount if a session exists and request could be verified, otherwise null.</returns>
        public IUserSession CheckSession()
        {
            if (VerifyClient())
            {
                if (!string.IsNullOrEmpty(SignOnSessionId))
                {
                    Guid guid = new Guid(SignOnSessionId);
                    IUserSession session = Data.UserSessions.GetUserSession(guid);
                    if (session == null)
                        throw new Exception("SignOnSession not found in database.");
                    return session;
                }
                else
                {
                    throw new Exception("SignOnSessionId not provided.");
                }
            }
            return null;
        }

        public override string SignatureContent
        {
            get { return ClientCode + "|" + Timestamp + "|" + SignOnSessionId; }
        }
    }

}
